Replicating Puruchuco


This notebook is based on the article Information Control in the Palace of Puruchuco - An Accounting Hierarchy in a Khipu Archive from Coastal Peru by Gary Urton and Carrie J. Brezine.

Code
import numpy as np
import pandas as pd
import khipu_kamayuq as kamayuq  # A Khipu Maker is known (in Quechua) as a Khipu Kamayuq
import khipu_qollqa as kq
from pandas import Series, DataFrame

# Plotly
import plotly
from plotly.offline import iplot, init_notebook_mode
import plotly.graph_objs as go
import plotly.express as px
import plotly.figure_factory as ff
plotly.offline.init_notebook_mode(connected = False)

(khipu_dict, all_khipus) = kamayuq.fetch_khipus()

1. The Puruchuco Hierarchy

The article references seven khipus:

CM009 had to be recreated from Carol Mackey’s notes and drawings of her 1970 doctoral thesis.

Code
UR063 = khipu_dict['UR063']
UR064 = khipu_dict['UR064']
UR066 = khipu_dict['UR066']
UR067 = khipu_dict['UR067']
UR068 = khipu_dict['UR068']
CM009 = khipu_dict['CM009']
UR073 = khipu_dict['UR073']

level_3 = [UR067, UR066]
level_2 = [UR064, CM009, UR068]
level_1 = [UR063, UR073]

2. Bottom to Middle Level - UR063/UR073 (Level I) and UR068 (Level II)

2.1 Examining the Khipu

An examination of Ascher Sum Relationships for UR063 reveals:

  • A set of left-handed pendant-pendant-sum relationships that are intriguing
  • A consistent, intentional pendant pendant color sum relationship featuring the value 12 or 13

The authors note that UR073 is broken.

We note first that khipu UR73 has been broken; it bears only 69 of what we surmise were originally about 111 pendant strings. The similarity between UR63 and UR73 in color patterning and numeric values is very close; thus we feel confident in assuming that these were originally a matching pair.

An examination of Ascher Sum Relationships for UR073 reveals:

  • An intriguing correspondence between right-handed and left-handed pendant-pendant-sums
  • Only one intentional pendant pendant color sum relationship, and it also features the value 12

The paper describes UR063 as having six subunits, labeled A-F

UR63 is organized by spacing and color seriation into six pendant string groupings, labeled a–f. The number of strings in each group is shown in brackets at the bottom of the columns. The six columns comprise: a) three sets of (5 x 4 =) 20 strings organized into five groups of four color- seriated strings; b) two sets of (3 x 4 + 2 x 3 =) 18 color-seriated strings; and c) one set of (3 x 4 + 3 =) 15 color-seriated strings.

Code
subunit_a = [aCord for aCord in UR063.pendant_cords()[0:20]]
subunit_b = [aCord for aCord in UR063.pendant_cords()[20:38]]
subunit_c = [aCord for aCord in UR063.pendant_cords()[38:56]]
subunit_d = [aCord for aCord in UR063.pendant_cords()[56:76]]
subunit_e = [aCord for aCord in UR063.pendant_cords()[76:96]]
subunit_f = [aCord for aCord in UR063.pendant_cords()[96:112]]

def cord_sum(cord_list): return sum([aCord.knotted_value() for aCord in cord_list])

def describe_cord_list(list_of_cord_lists, titlelist=None):
    description = ""
    for i, aCordList in enumerate(list_of_cord_lists):
        title = f"{titlelist[i]}  " if titlelist else ''
        list_len = len(aCordList)
        contents = f"{[aCord.knotted_value() for aCord in aCordList]}"[1:-1]   
        description += f"{title}({list_len})  {contents}\n"
    if len(list_of_cord_lists) > 1: description += "\n"
    for i, aCordList in enumerate(list_of_cord_lists):
        title = f"{titlelist[i]}  " if titlelist else ''
        list_len = len(aCordList)
        contents = f"{[(aCord.level_name, aCord.longest_ascher_color()) for aCord in aCordList]}"[1:-1].replace("\'", "")       
        description += f"{title}({list_len})  {contents}\n"
    print(description)

describe_cord_list([subunit_f, subunit_e, subunit_d, subunit_c, subunit_b, subunit_a],
                   ['subunit_f', 'subunit_e', 'subunit_d', 'subunit_c', 'subunit_b', 'subunit_a'])
subunit_f  (15)  0, 0, 0, 0, 1, 0, 1, 0, 5, 0, 1, 0, 219, 6, 0
subunit_e  (20)  0, 0, 0, 0, 2, 0, 1, 0, 2, 0, 1, 0, 10, 1, 0, 1, 157, 7, 12, 3
subunit_d  (20)  2, 0, 0, 0, 3, 1, 0, 0, 1, 1, 0, 0, 12, 1, 0, 0, 367, 8, 8, 2
subunit_c  (18)  0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 9, 2, 1, 0, 148, 7, 2
subunit_b  (18)  0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 1, 0, 12, 1, 1, 229, 6, 13
subunit_a  (20)  0, 0, 1, 0, 2, 0, 0, 0, 1, 0, 0, 1, 9, 1, 1, 0, 127, 12, 9, 1

subunit_f  (15)  (p97, W), (p98, AB), (p99, GG), (p100, W:KB), (p101, W), (p102, AB), (p103, GG), (p104, W:KB), (p105, W), (p106, AB), (p107, GG), (p108, W:KB), (p109, W), (p110, AB), (p111, GG)
subunit_e  (20)  (p77, W), (p78, AB), (p79, GG), (p80, W:KB), (p81, W), (p82, AB), (p83, GG), (p84, W:KB), (p85, W), (p86, AB), (p87, GG), (p88, W:KB), (p89, W), (p90, AB), (p91, GG), (p92, W:KB), (p93, W), (p94, AB), (p95, GG), (p96, W:KB)
subunit_d  (20)  (p57, W), (p58, MB), (p59, GG), (p60, W:KB), (p61, W), (p62, AB), (p63, GG), (p64, W:KB), (p65, W), (p66, AB), (p67, GG), (p68, W:KB), (p69, W), (p70, AB), (p71, GG), (p72, W:KB), (p73, W), (p74, AB), (p75, GG), (p76, W:KB)
subunit_c  (18)  (p39, W), (p40, AB), (p41, GG), (p42, W), (p43, AB), (p44, GG), (p45, W:KB), (p46, W), (p47, AB), (p48, GG), (p49, W:KB), (p50, W), (p51, MB), (p52, GG), (p53, W:KB), (p54, W), (p55, MB), (p56, GG)
subunit_b  (18)  (p21, W), (p22, AB), (p23, GG), (p24, W:KB), (p25, W), (p26, AB), (p27, GG), (p28, W:KB), (p29, W), (p30, AB), (p31, GG), (p32, W:KB), (p33, W), (p34, W:AB), (p35, GG), (p36, W), (p37, AB), (p38, GG)
subunit_a  (20)  (p1, W), (p2, AB), (p3, GG), (p4, W:GG), (p5, W), (p6, MB), (p7, GG), (p8, W:GG), (p9, W), (p10, MB), (p11, GG), (p12, W:GG), (p13, W), (p14, MB), (p15, GG), (p16, W:GG), (p17, W), (p18, MB), (p19, GG), (p20, W:GG)

There are some discrepancies, between the data and the article.

Code
article_subunit_a=[1, 2, 1, 9, 1, 127, 12, 9, 1]
article_subunit_b=[1, 2, 1, 12, 1, 1, 229, 6, 13]
article_subunit_c=[1, 1, 9, 2, 1, 148, 7, 2]
article_subunit_d=[2, 3, 1, 1, 1, 12, 1, 367, 8, 8, 2]
article_subunit_e=[2, 1, 2, 1, 10, 1, 1, 157, 7, 12, 3]
article_subunit_f=[1, 1, 5, 1, 219, 6]

print(f"{cord_sum(subunit_a)=} - {sum(article_subunit_a)=} = {cord_sum(subunit_a)-sum(article_subunit_a)}")
print(f"{cord_sum(subunit_b)=} - {sum(article_subunit_b)=} = {cord_sum(subunit_b)-sum(article_subunit_b)}")
print(f"{cord_sum(subunit_c)=} - {sum(article_subunit_c)=} = {cord_sum(subunit_c)-sum(article_subunit_c)}")
print(f"{cord_sum(subunit_d)=} - {sum(article_subunit_d)=} = {cord_sum(subunit_d)-sum(article_subunit_d)}")
print(f"{cord_sum(subunit_e)=} - {sum(article_subunit_e)=} = {cord_sum(subunit_e)-sum(article_subunit_e)}")
print(f"{cord_sum(subunit_f)=} - {sum(article_subunit_f)=} = {cord_sum(subunit_f)-sum(article_subunit_f)}")
cord_sum(subunit_a)=165 - sum(article_subunit_a)=163 = 2
cord_sum(subunit_b)=266 - sum(article_subunit_b)=266 = 0
cord_sum(subunit_c)=172 - sum(article_subunit_c)=171 = 1
cord_sum(subunit_d)=406 - sum(article_subunit_d)=406 = 0
cord_sum(subunit_e)=197 - sum(article_subunit_e)=197 = 0
cord_sum(subunit_f)=233 - sum(article_subunit_f)=233 = 0

So we are off by 3.

Let’s sort subunits by color sum:

Code
def sum_cord_values(aCordList):
    sums_by_color = {aColor:0 for aColor in sorted(set([aCord.longest_ascher_color() for aCord in aCordList]))}
    for aCord in aCordList: sums_by_color[aCord.longest_ascher_color()] += aCord.knotted_value()
    return sums_by_color

print(f"{sum_cord_values(subunit_a)=}")
print(f"{sum_cord_values(subunit_b)=}")
print(f"{sum_cord_values(subunit_c)=}")
print(f"{sum_cord_values(subunit_d)=}")
print(f"{sum_cord_values(subunit_e)=}")
print(f"{sum_cord_values(subunit_f)=}")
sum_cord_values(subunit_a)={'AB': 0, 'GG': 11, 'MB': 13, 'W': 139, 'W:GG': 2}
sum_cord_values(subunit_b)={'AB': 6, 'GG': 15, 'W': 244, 'W:AB': 1, 'W:KB': 0}
sum_cord_values(subunit_c)={'AB': 1, 'GG': 3, 'MB': 9, 'W': 159, 'W:KB': 0}
sum_cord_values(subunit_d)={'AB': 11, 'GG': 8, 'MB': 0, 'W': 385, 'W:KB': 2}
sum_cord_values(subunit_e)={'AB': 8, 'GG': 14, 'W': 171, 'W:KB': 4}
sum_cord_values(subunit_f)={'AB': 6, 'GG': 2, 'W': 225, 'W:KB': 0}

The sum of these subunits, in turn is supposed to roughly equal UR068 cords 34-53

Code
subunit_UR068 = [aCord for aCord in UR068.pendant_cords()[33:53]]
describe_cord_list([subunit_UR068])

sum_UR063 = cord_sum(subunit_a) + cord_sum(subunit_b) + cord_sum(subunit_c) + cord_sum(subunit_d) + cord_sum(subunit_e) + cord_sum(subunit_f)
print(f"{sum_UR063=}")
partial_sum_UR068 = cord_sum(subunit_UR068)
print(f"{partial_sum_UR068=}")
(20)  2, 0, 1, 0, 8, 1, 1, 0, 8, 1, 3, 1, 57, 5, 6, 2, 1236, 46, 59, 10
(20)  W, AB, GG, W:GG, W, AB, AB:GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB

sum_UR063=1439
partial_sum_UR068=1447

2.2 Aligning UR068 Sums with UR063

It takes some finessing to make the Puruchuco scheme work for aligning the sums of UR068 to match a section of UR063. If you align the sections by color, and insert a “null cord” holder, when needed, it aligns well.

Here’s a tabular “excel” view similar to Figure 6 of the article:

KHIPU # CORDS
UR068 34-53 p34 p35 p36 p37 p38 p39 p40 p41 p42 p43 p44 p45 p46 p47 p48 p49 p50 p51 p52 p53
20 W AB GG W:GG W AB AB:GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
20 2 0 1 0 8 1 1 0 8 1 3 1 57 5 6 2 1236 46 59 10
UR063 (Sum) 2 0 1 0 9 1 1 0 8 2 3 1 57 6 4 1 1247 46 44 6
UR063
subunit_f 15 0 0 0 0 1 0 1 0 5 0 1 0 219 6 0
subunit_e 20 0 0 0 0 2 0 1 0 2 0 1 0 10 1 0 1 157 7 12 3
subunit_d 20 2 0 0 0 3 1 0 0 1 1 0 0 12 1 0 0 367 8 8 2
subunit_c 18 0 0 0 null cord 1 0 0 0 1 1 0 0 9 2 1 0 148 7 2
subunit_b 18 0 0 0 0 1 0 0 0 2 0 1 0 12 1 1 null cord 229 6 13
subunit_a 20 0 0 1 0 2 0 0 0 1 0 0 1 9 1 1 0 127 12 9 1
subunit_f 15 W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG
subunit_e 20 W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
subunit_d 20 W MB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
subunit_c 18 W AB GG null cord W AB GG W:KB W AB GG W:KB W MB GG W:KB W MB GG
subunit_b 18 W AB GG W:KB W AB GG W:KB W AB GG W:KB W W:AB GG null cord W AB GG
subunit_a 20 W AB GG W:GG W MB GG W:GG W MB GG W:GG W MB GG W:GG W MB GG W:GG

3. Middle to Top Level - UR068 (Level II) and UR067 (Level III)

3.1 Examination of UR064, CM009 and UR068 - Level II

3.1.3 UR064

An examination of URO64’s pendant-pendant sums reveals intentional pendant summing on top of the summing of the Puruchuco hierarchy. This is especially evident in the last few cords of the last few clusters - a hallmark of left-handed pendant-pendant sums.

3.1.2 CM009

An examination of CM009’s sum relationships reveals intentional pendant summing on top of the summing of the Puruchuco hierarchy, as well as evidence of Ascher decreasing clusters.

3.1.3 UR068

An examination of URO68’s pendant-pendant sums reveals intentional pendant summing on top of the summing of the Puruchuco hierarchy.

Additionally 4 of the clusters are Ascher decreasing groups.

3.2 Examination of UR066 and UR067 - Level III

3.2.1 UR067

As befitting a “high-level khipu”, UR067 has only two pendant-pendant sum relationships, and those seem largely unintentional and of small value.

3.2.2 UR066

As befitting a “high-level khipu”, UR066 has only two pendant-pendant sum relationships, and those seem largely unintentional and of small value. However, on visual examination it last four (of six) clusters appear to be rough ascher decreasing groups.

3.3 Aligning UR068 Sums with UR067

Continuing the replication of the article, we examine the interaction between the next two levels.

Code
subunit_a = [aCord for aCord in UR068.pendant_cords()[13:33]]
subunit_b = [aCord for aCord in UR068.pendant_cords()[33:53]]
subunit_c = [aCord for aCord in UR068.pendant_cords()[53:73]]
describe_cord_list([subunit_c, subunit_b, subunit_a],
                   ['subunit_c', 'subunit_b', 'subunit_a'])
subunit_c  (20)  1, 0, 0, 0, 3, 1, 1, 0, 6, 1, 3, 0, 43, 3, 2, 2, 459, 44, 39, 13
subunit_b  (20)  2, 0, 1, 0, 8, 1, 1, 0, 8, 1, 3, 1, 57, 5, 6, 2, 1236, 46, 59, 10
subunit_a  (20)  3, 0, 0, 0, 8, 2, 1, 0, 8, 1, 3, 1, 56, 5, 4, 1, 1213, 43, 64, 17

subunit_c  (20)  W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB
subunit_b  (20)  W, AB, GG, W:GG, W, AB, AB:GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB
subunit_a  (20)  W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB, W, AB, GG, W:KB
Code
subunit_UR067 = [aCord for aCord in UR067.pendant_cords()[12:32]]
describe_cord_list([subunit_UR067])

sum_UR068 = cord_sum(subunit_a) + cord_sum(subunit_b) + cord_sum(subunit_c)
print(f"{sum_UR068=}")
partial_sum_UR067 = cord_sum(subunit_UR067)
print(f"{partial_sum_UR067=}")
(20)  0, 0, 1, 0, 19, 4, 3, 0, 22, 3, 9, 2, 156, 13, 12, 0, 2904, 133, 161, 40
(20)  W, AB, GG, W:KB, W, MB, YG, W:KB, W, AB, AB:GG, W:KB, W, AB, GG, AB:KB, W, AB, AB, W:KB

sum_UR068=3498
partial_sum_UR067=3482

A similar “excel” table view of the sums for UR068 to UR067

KHIPU # CORDS
UR067 p13-p32 p13 p14 p15 p16 p17 p18 p19 p20 p21 p22 p23 p24 p25 p26 p27 p28 p29 p30 p31 p32
20 W AB GG W:KB W MB YG W:KB W AB AB:GG W:KB W AB GG AB:KB W AB AB W:KB
20 0 0 1 0 19 4 3 0 22 3 9 2 156 13 12 0 2904 133 161 40
UR068 p14-p73 6 0 1 0 19 4 3 0 22 3 9 2 156 13 12 5 2908 133 162 40
subunit_c 20 1 0 0 0 3 1 1 0 6 1 3 0 43 3 2 2 459 44 39 13
subunit_b 20 2 0 1 0 8 1 1 0 8 1 3 1 57 5 6 2 1236 46 59 10
subunit_a 20 3 0 0 0 8 2 1 0 8 1 3 1 56 5 4 1 1213 43 64 17
subunit_c 20 W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
subunit_b 20 W AB GG W:GG W AB AB:GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB
subunit_a 20 W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB W AB GG W:KB

4. Local vs State Khipus

The article mentions the following hypothesis:

We suggest that khipus may have contrasting number qualities depending on whether they represented instructions coming from the state administration to a local accounting center, or records produced within a local accounting center with regard to existing community resources. In the first circumstance, we suspect that khipu values would have tended to be even decimal values or calculations of values in standard proportional shares.12 If a khipu account was compiled from within some local administrative center to be sent upward to higher-level officials, counts of resources could be expected to have reflected the vagaries of the natural distribution of items in society. Such numbers are less likely to be whole and rounded or perfectly proportional. This is partly because there are many more “interdecimal” values (e.g., 41, 89, 53) than there are full decimal ones (e.g., 10, 20, 100).

Let’s briefly review the Benford Match for the 7 khipus

Code
# Read in the Fieldmark and its associated dataframe and match dictionary
from fieldmark_khipu_summary import Fieldmark_Benford_Match
aFieldmark = Fieldmark_Benford_Match()
fieldmark_df() = aFieldmark.fieldmark_df()
def benford_match(aKhipuName):
    return fieldmark_df()[fieldmark_df().kfg_name== aKhipuName].iloc[0].benford_match


# Remember:
level_3 = ['UR067', 'UR066']
level_2 = ['UR064', 'CM009', 'UR068']
level_1 = ['UR063', 'UR073']

for khipu_name in level_3:
    print(f"III - {khipu_name}  {benford_match(khipu_name):2.2f}")
print()
for khipu_name in level_2:
    print(f"II - {khipu_name}  {benford_match(khipu_name):2.2f}")
print()
for khipu_name in level_1:
    print(f"I - {khipu_name}  {benford_match(khipu_name):2.2f}")
III - UR067  0.60
III - UR066  0.63

II - UR064  0.66
II - CM009  0.80
II - UR068  0.71

I - UR063  0.49
I - UR073  0.45

These differences are large enough to question the hypothesis proposed in the article.